#【计算等额本息还款计划书】

'''
作业题目：给出等额本金和等额本息的每期（每月）应还本金和利息的金额的公式推导。假设年利率i=6%，初始借出本金A=100万元，借出期限分别考虑N=10、20、30年。定义函数计算出每期（每月）应还本金和利息、剩余的未还本金、未还利息（即还款计划书）。
i: 每期贷款利率（这里是月利率6%/12=0.5%）, i: interest rate
n: the total number of periods
Bk: 第k期末的剩余未还的本金（第0期B0是期初的总贷到的本金）, B: the bulk of principle unpaid at the end of period k
Ak: 第k期应还本息, Ak: a period’s paid money
Ik:第k期应还利息, Ik: interest of a period
Pk: 第k期应还本金, Pk: principle paid in a period 
Rk: 第k期末的剩余未还的利息, Rk: residual interest at the end of the kth period
'''
import sys

#所有贷款计算器的父类
class LoanCalculator():
	'''贷款计算类，这是一个抽象类'''

	def __init__(self, i, n, B0):
		'''
		【初始化参数注意】i:月利率, n: 总月数，B0: 总还款金额（元）
		'''

		#对用一个符号命名的变量（不符合变量命名需有信息含义的规则），必须加注释，别人才能看得懂
		self.i = i #i: 每期贷款利率（这里是月利率6%/12=0.5%）, i: interest rate
		self.n = n #n: the total number of periods
		self.B0 = B0 #Bk: 第k期末的剩余未还的本金（第0期B0是期初的总贷到的本金）, B: the bulk of principle unpaid at the end of period k
	
		  
	def a_period_paid(self, k):
		'''方法描述（功能，参数，返回值是什么，这样别人才能看得懂，能调用对）'''
		
		return None

	def a_period_principal(self, k): 
		'''方法描述（功能，参数，返回值是什么，这样别人才能看得懂，能调用对）'''
		return None

	def a_period_interest(self, k):
		'''方法描述（功能，参数，返回值是什么，这样别人才能看得懂，能调用对）'''
		return None

	def residual_principal(self, k):
		'''方法描述（功能，参数，返回值是什么，这样别人才能看得懂，能调用对）'''
		return None

	def residual_interest(self, k):
		'''方法描述（功能，参数，返回值是什么，这样别人才能看得懂，能调用对）'''
		return None

	def print_loan_plan(self):
		'''方法描述（功能，参数，返回值是什么，这样别人才能看得懂，能调用对）'''
		return


#等额本息计算器
class EqualPaidLoanCalculator(LoanCalculator):
	def __init__(self, i, n, B0):
		'''
		【初始化参数注意】i:月利率（小数，如0.06）, n: 总月数（整数，如12*20），B0: 总还款金额（单位：元，如1000000.0）
		'''
		LoanCalculator.__init__(self, i, n, B0)
		self.A_k = None #新定义属性A_k，第k期的还款金额（因为很多方法都要访问这个变量）

	def a_period_paid(self, k):
		'''计算等额本息的一期（默认是月）的还款金额（本息）
		  参数：k，第k期
		  返回值：第k期应还款金额。
		  【使用注意】：先调用该方法计算出属性A_k后，后面其它方法才能正常工作
		  '''
		#A_k = self.B0*self.i/(1-(1+self.i)**(-self.n)) #注意：该A_k的作用域仅限于该方法内部，外部访问不了的
		self.A_k = self.B0*self.i/(1-(1+self.i)**(-self.n))#只能定义成属性
		return self.A_k

	def a_period_principal(self, k): 
		'''计算等额本息的一期（默认是月）的还款金额中的本金部分
		  参数：k，第k期
		  返回值：第k期应还款金额中的本金部分。
		  '''
		if self.A_k == None:#区分已知错误，打印响应的出错提示信息，下同
			print('请先调用a_period_paid方法来计算属性A_k(第k期应还本息)!')
			return None
			sys.exit(0)
		else:
			P_k = self.A_k*(1+self.i)**(k-1-self.n)
			return P_k

	def a_period_interest(self, k):
		'''计算等额本息的一期（默认是月）的还款金额中的利息部分
		  参数：k，第k期
		  返回值：第k期应还款金额中的利息部分
		'''
		I_k = self.A_k*(1-(1+self.i)**(k-1-self.n))
		return I_k

	def residual_principal(self, k):
		'''计算等额本息的第k期还款后剩余的总的未还本金
		  参数：k，第k期
		  返回值：第k期末总剩余未还本金
		'''
		B_k = self.A_k*(1-(1+self.i)**(k-self.n))/self.i
		return B_k

	def residual_interest(self, k):
		'''计算等额本息的第k期还款后剩余的总的未还利息
		  参数：k，第k期
		  返回值：第k期末总剩余未还利息
		'''
		R_k = (self.n-k)*self.A_k+self.i*self.A_k*((1+self.i)**(k-self.n)-1)
		return R_k

	def print_loan_plan(self):
		'''打印各期的还款计划书'''
		print('总借款: {}, 月利率=年利率/12: {}, 借款月数: {}'.format(self.B0, self.i, self.n))
		for k in range(1, self.n+1):
			print('第{}月:\t本期应还{} (本金部分:{}, 利息部分:{})\t剩余未还本金:{}\t剩余未还利息:{}'.format(
																		k,
																		round(self.a_period_paid(k)),
																		round(self.a_period_principal(k)),
																		round(self.a_period_interest(k)),
																		round(self.residual_principal(k)),
																		round(self.residual_interest(k))
																		)
				  )

class EquPaidPriCalc(LoanCalculator):
	''' A_k 每月付款 B_k 剩余本金 B_0 初始本金, R_i 剩余利息'''
	def __init__(self, i, n, B0):
		LoanCalculator.__init__(self,i,n,B0)
		self.A_k = None
		self.B_k = B0
		self.R_i = 0
		t = B0
		ans = 0
		for k in range(0,n):
			ans += t * i
			t -= B0 / n
		self.R_i = ans
		
	def pri_per_mon(self,i,n,B0): ## 每月付本金部分
		self.P_k = B0 / n
		return self.P_k
	def ini_per_mon(self,i,n,B0): ## 每月付利息部分
		self.I_k = self.B_k * i
		return self.I_k
	def paid_per_mon(self,i,n,B0): ## 每月付款
		self.A_k = B0 / n + self.B_k * i
		return self.A_k
	def res_pri(self,i,n,B0): ## 剩余本金
		self.B_k -= self.P_k
		return self.B_k
	def res_ini(self,i,n,B0): ## 剩余利息
		self.R_i -= self.I_k
		return self.R_i
	def res_tot(self,i,n,B0): ## 剩余本息和
		self.tot = self.B_k + self.R_i
		return self.tot
	def print_loan_plan(self,i,n,B0):
		'''打印各期的还款计划书'''
		print('总借款: {}, 月利率=年利率/12: {}, 借款月数: {}'.format(self.B0, self.i, self.n))
		for k in range(1, self.n+1):
			print('第{}月:\t本期应还{} (本金部分:{}, 利息部分:{})\t剩余未还本金:{}\t剩余未还利息:{}'.format(
																		k,
																		round(self.paid_per_mon(i,n,B0)),
																		round(self.pri_per_mon(i,n,B0)),
																		round(self.ini_per_mon(i,n,B0)),
																		round(self.res_pri(i,n,B0)),
																		round(self.res_ini(i,n,B0))
																		)
				  )
#主程序
if __name__ == '__main__':
	equal_paid_calc = EquPaidPriCalc(0.06/12, 20*12, 1000000) #【调用时建议加注释，别人才能正确调用】i:月利率, n: 总月数，B0: 总还款金额（元）
	equal_paid_calc.print_loan_plan(0.06/12, 20*12, 1000000)

	
'''
【总结】
实验结果讨论：
1. 靠前的每期还款的本金小于还款的利息，换句话说，你初始时还的主要是银行的利息；
2. 到第60个月末，还欠银行很多本金（初始100万借款还有84万本金未还）；
3. 【提前还本，减轻债务】，不仅可以少还利息，还可以减少每月还款金额，减轻断供风险。
4. 有人认为，不提前还款，利用手头现金做股票投资，但是，***长期来看***，年化10%收益率的投资者（包括机构）占比较少，更有不少人因做中高风险投资而亏钱。因此，需平衡高风险理财和低风险理财（提前还本避免较高贷款利息，等于低于低风险理财）。

建议：对个人对公司，需常考虑减轻债务，以减轻财务风险。


【研究建议】
任何一项研究（有研究意味的报告）都要*详细*讨论结果。研究报告/论文不是本科低年级的课程设计，不能输出结果就完，还要讨论实验的结果的含义。


【代码建议】
1. 代码的可读性（靠注释），规范性（参考：实用规约PPT，面向对象设计原则）。
2. 宁可做慢点，边做边检查（看、测试），保证质量。否则，后续改（重过一遍）花的时间比第一次做的还多。


【工作建议】平时工作，把事情分散到平时，慢慢磨，既能保证质量，心理压力又不大。提前计划：一年之计在于春，一月之计在于月初，一周之计在于周一，一日之计在于晨！

'''


